<html>
    <head>
        <title>使用引用操作DOM</title>
    </head>
    <script>
        // 使用引用操作DOM
                /*
                    React 会自动更新DOM以匹配你的渲染输出，因此你的组件不需要经常操作DOM。
                    然而，有时你可能需要访问Reac管理的DOM元素----例如，聚焦一个节点、滚动到它或测量它的大小和位置。
                    在React中没有内置的方法来做这些事情，所以你需要一个DOM节点的引用。
                */

                // 一、获取节点的引用
                       // 要访问 React管理的DOM节点，首先，要入 useRef 钩子：
                            import {useRef} from 'react';

                       // 然后，使用useRef钩子在你的组件内部创建一个引用：
                            const myRef = useRef(null);

                       // 最后，将你的引用作为 ref 属性传递给你想要获取DOM节点的JSX标签：
                            <div ref={myRef}>
                            </div>

                       /*
                            useRef钩子返回一个对象，该对象有一个名为 current 的属性。 
                            最初， myRef.current 将是 null 。
                            当React为这个<div> 创建一个"DOM节点"时，React会将这个"节点"的"引用"放到 myRef.current 中。
                            然后，你可以从"事件处理程序"访问此DOM节点并使用其上定义的"内置浏览器API"
                       */

                          // eg: 聚焦一个节点
                                 import {useRef} from 'react';
                                 export default function Form(){
                                     const inputRef = useRef(null); // 为组件创建一个引用

                                     function handleClick(){
                                        inputRef.current.focus();
                                     }

                                     return (
                                        <>
                                            <input ref={inputRef}/>
                                            <button onClick={handleClick}>
                                                focus the input
                                            </button>
                                        </>
                                     );
                                 }

                          // eg: 滚动到某个元素
                                import { useRef } from 'react';

                                export default function CatFriends() {
                                    const firstCatRef = useRef(null);  // 使用useRef钩子创建一个引用
                                    const secondCatRef = useRef(null);
                                    const thirdCatRef = useRef(null);

                                    function handleScrollToFirstCat() {
                                        firstCatRef.current.scrollIntoView({
                                            behavior: 'smooth',
                                            block: 'nearest',
                                            inline: 'center'
                                        });
                                    }

                                    function handleScrollToSecondCat() {
                                        secondCatRef.current.scrollIntoView({
                                            behavior: 'smooth',
                                            block: 'nearest',
                                            inline: 'center'
                                        });
                                    }

                                    function handleScrollToThirdCat() {
                                        thirdCatRef.current.scrollIntoView({
                                            behavior: 'smooth',
                                            block: 'nearest',
                                            inline: 'center'
                                        });
                                    }

                                    return (
                                        <>
                                            <nav>
                                                <button onClick={handleScrollToFirstCat}>
                                                    Neo
                                                </button>
                                                <button onClick={handleScrollToSecondCat}>
                                                    Millie
                                                </button>
                                                <button onClick={handleScrollToThirdCat}>
                                                    Bella
                                                </button>
                                            </nav>
                                            <div>
                                                <ul>
                                                    <li>
                                                        <img
                                                            src="https://placecats.com/neo/300/200"
                                                            alt="Neo"
                                                            ref={firstCatRef}
                                                        />
                                                    </li>
                                                    <li>
                                                        <img
                                                            src="https://placecats.com/millie/200/200"
                                                            alt="Millie"
                                                            ref={secondCatRef}
                                                        />
                                                    </li>
                                                    <li>
                                                        <img
                                                            src="https://placecats.com/bella/199/200"
                                                            alt="Bella"
                                                            ref={thirdCatRef}
                                                        />
                                                    </li>
                                                </ul>
                                            </div>
                                        </>
                                    );
                                }
            // 二、访问另一个组件的DOM节点
                        /*
                            当你把一个引用放在输出浏览器元素的内置组件上时，比如<input> ，React会将该引用的current 属相设置为相应的DOM节点。
                            
                            
                        */
                            /*
                                但是,如果你尝试在自己的组件添加引用，例如<MyInput />,默认情况下你将得到null．
                                这是一个演示他的例子：
                            */
                                import { useRef } from 'react';

                                function MyInput(props) {
                                    return <input {...props} />;
                                }

                                export default function MyForm() {
                                    const inputRef = useRef(null);

                                    function handleClick() {
                                        inputRef.current.focus();
                                    }

                                    return (
                                        <>
                                            <MyInput ref={inputRef} />
                                            <button onClick={handleClick}>
                                                聚焦输入框
                                            </button>
                                        </>
                                    );
                                }

                          /*
                             为了帮助您注意到这个问题，React还会向控制台打印一条错误信息：

                                Warning: Function components cannot be given refs. 
                                        Attempts to access this ref will fail. 
                                        Did you mean to use React.forwardRef()?


                              发生这种情况是默认情况下，React不允许组件访问其他组件的DOM节点。
                              甚至自己的自组件也不行！
                              这是故意的。Refs 是一种"应急机制"，应该谨慎使用。手动操作另一个节点的DOM会使你的代码"更脆弱"。


                              相反，想要保留其DOM节点的组件必须"选择该行为"(意思是组件自己想暴露DOM节点，才能被别的组件引用)。
                              一个组件可以指定将它的ref"转发"给一个子组件。
                          */
                                // 下面是MyInput 如何使用forwardRef API：
                                    const MyInput = forwordRef((props,ref)=>{
                                        return <input {...props} ref ={ref}/>
                                    });

                                /*
                                    它是这样工作的：

                                        1、<MyInput ref={inputRef}/> 告诉React将对应的DOM节点放入 inputRef.current中。但是，这取决于MyInput组件是否允许这种行为，
                                                默认情况下是不允许的。

                                        2、MyInput 组件时使用 forwardRef声明的。 这让从上面接收的 inputRef作为第二个参数ref 传入组件，第一个参数是props.

                                        3、MyInput  组件将自己接收到的 ref 传递给它内部的<input>
                                */
                                        // 现在，单击按钮聚焦输入框起作用了：
                                            import {forwordRef,useRef} from 'react';

                                            // 通过forwardRef()钩子，可以将对当前组件的引用"转发"给当前组件的"子组件"
                                            const MyInput = forwardRef((props,ref)=>{
                                                    return <input {...props} ref={ref}/>;
                                            });

                                            export default function Form(){
                                                const inputRef = useRef(null);

                                                function handleClick(){
                                                    inputRef.current.focus();
                                                }

                                                return (
                                                    <>
                                                        <MyInput ref={inputRef}/>
                                                        <button onClick ={handleClick}>
                                                            聚焦输入框
                                                        </button>
                                                    </>
                                                );
                                            }
                                                
                                        /*
                                            在设计系统中，将低级组件(如按钮、输入框)的ref转发到它们的DOM节点是一种常见模式。
                                            另外一方面，像表单、列表、或者页面段落这样高级组件通常不会暴露它们的DOM节点，以避免对DOM结构的以外依赖。
                                        */


            // 三、React 何时添加Refs
                        /*
                            在React中，每次更新都分为"两个阶段"：
                                    1、在渲染阶段， React调用你的组件来确定屏幕上应该显示什么。
                                    2、在提交阶段，React把变更应用于DOM
                                
                            通常，你不希望在渲染期间访问refs.  这也适用于保存DOM节点的refs。

                            在第一次渲染期间，DOM节点尚未创建，因此 ref.current 将为null。
                            在渲染更新的过程中，DOM节点还没有更新。所以曲度它们还为时过早。

                            React在提交阶段设置 ref.current。在更新DOM之前，React将受影响的ref.current 值设置为null。更新DOM后，React立即将他们设置到相应的DOM节点。

                            "通常，你将从事件处理程序中访问refs"。
                            如果你想使用ref执行某些操作，但没有特定的事件可以执行此操作，你可能需要一个Effect。我们将在下一页讨论Effect
                        */


            // 四、使用refs操作DOM的最佳实践
                        /*
                                refs 是一种脱围机制。
                                你应该只在你必须"跳出react"时使用它们。这方面的常见示例包括: 管理焦点、滚动位置或调用React未暴露的浏览器API。

                                如果你坚持聚焦和滚动等非破坏性操作，应该不会遇到任何问题。
                                但是，如果你尝试手动修改 DOM，则可能会与 React 所做的更改发生冲突。
                        */
                            /*
                                为了说明这个问题，这个例子包括一条欢迎消息和两个按钮。
                                第一个按钮使用 条件渲染 和 state 切换它的显示和隐藏，就像你通常在 React 中所做的那样。
                                第二个按钮使用 remove() DOM API 将其从 React 控制之外的 DOM 中强行移除.
                                尝试按几次“通过 setState 切换”。该消息会消失并再次出现。
                                然后按 “从 DOM 中删除”。这将强行删除它。最后，按 “通过 setState 切换”：
                            */
                                import { useState, useRef } from 'react';

                                export default function Counter() {
                                    const [show, setShow] = useState(true);
                                    const ref = useRef(null);

                                    return (
                                        <div>
                                        <button
                                            onClick={() => {
                                                setShow(!show);
                                            }}>
                                            通过 setState 切换
                                        </button>
                                        <button
                                            onClick={() => {
                                                ref.current.remove();
                                            }}>
                                            从 DOM 中删除
                                        </button>
                                            {show && <p ref={ref}>Hello world</p>}
                                        </div>
                                    );
                                }
                                /*
                                    在你手动删除 DOM 元素后，尝试使用 setState 再次显示它会导致崩溃。
                                    这是因为你更改了 DOM，而 React 不知道如何继续正确管理它。

                                    "避免更改由 React 管理的 DOM 节点。"
                                    对 React 管理的元素进行修改、添加子元素、从中删除子元素会导致不一致的视觉结果，或与上述类似的崩溃。

                                    但是，这并不意味着你完全不能这样做。
                                    它需要谨慎。 你可以安全地修改 React 没有理由更新的部分 DOM。 
                                    例如，如果某些 <div> 在 JSX 中始终为空，React 将没有理由去变动其子列表。 因此，在那里手动增删元素是安全的。
                                */
    </script>
</html>